use std::io;
use std::cmp;
use std::time::Instant;
use std::time::Duration;
use termion::color;
use termion::event::Key;

use crate::Document;
use crate::Row;
use crate::Terminal;

const STATUS_BG_COLOR: color::Rgb = color::Rgb(239, 239, 239);  
const STATUS_FG_COLOR: color::Rgb = color::Rgb(63, 63, 63);
const SYMBOL_FG_COLOR: color::Rgb = color::Rgb(0, 191, 255);

const QUIT_TIMES: u8 = 3;

pub struct StatusMsg {
    text: String,
    time: Instant,
}

#[derive(Default, Debug)]
pub struct Position {
    pub x: usize,
    pub y: usize,
}

/**
 * 
 */
pub struct Editor {
    pub empty_symbol: char,         //a symbol indicates the row is empty
    pub should_quit: bool,          //represent whether to quit or not
    pub document: Document,         //
    pub terminal: Terminal,         //
    pub cursor_position: Position,  //the position of the cursor
    pub offset_position: Position,  //represent the offset of the document
    pub status_msg: StatusMsg,      //status message
    pub quit_times: u8, 
}


impl Editor {
    pub fn default() -> Self {
        let mut initial_status_msg = String::from("HELP: Ctrl+C = quit | Ctrl+S = save");
        let args: Vec<String> = std::env::args().collect();
        let document = if args.len() > 1 {
            let file_name = &args[1];
            let doc = Document::open(file_name);
            if let Ok(doc) = doc {
                doc
            }else {
                initial_status_msg = format!("Err: Couldn't open file: {}", file_name);
                Document::default()
            }
        }else {
            Document::default()
        };

        Self {
            empty_symbol: '~',
            should_quit: false,
            document,
            terminal: Terminal::default().expect("failed to initialize a terminal"),
            cursor_position: Position::default(),
            offset_position: Position::default(),
            status_msg: StatusMsg::from(initial_status_msg),
            quit_times: QUIT_TIMES,
        }
    }
    
    pub fn run(&mut self) {
        loop {
            if let Err(err) = self.refresh_screen() {
                die(err);
            }

            if self.should_quit {
                Terminal::clear_screen();
                Terminal::cursor_position(&Position::default());
                break;
            }

            if let Err(err) = self.process_key() {
                die(err);
            }
        }
    }

    pub fn refresh_screen(&mut self) -> Result<(), io::Error>{
        Terminal::hide_cursor();

        self.draw_rows();           //document strings
        self.draw_status_bar();     //status bar
        self.draw_msg_bar();        //msg bar

        Terminal::cursor_position(&Position {
            x: self.cursor_position.x.saturating_sub(self.offset_position.x),
            y: self.cursor_position.y.saturating_sub(self.offset_position.y),
        });
        Terminal::show_cursor();
        Terminal::flush()?;
        Ok(())
    }

    pub fn process_key(&mut self) -> Result<(), io::Error> {
        let key = Terminal::read_key()?;

        match key {
            Key::Ctrl('c') => {         //set quit to true
                if self.quit_times > 0 && self.document.is_dirty() {
                    self.status_msg = StatusMsg::from(
                        format!(
                            "WARNING:File has unsaved changes. Press Ctrl+C {} times to quit.",
                            self.quit_times,
                    ));

                    self.quit_times -= 1;
                    return Ok(());
                }
                self.should_quit = true;
            }
            Key::Ctrl('s') => self.save(),
            Key::Char('\n') => {
                self.document.insert(&self.cursor_position, '\n');
                self.move_cursor(Key::Down);
                self.move_cursor(Key::Home);
            }
            Key::Char(c) => {
                self.document.insert(&self.cursor_position, c);
                self.move_cursor(Key::Right);
            }
            Key::Delete => {
                self.document.delete(&self.cursor_position);
            }
            Key::Backspace => {
                if self.cursor_position.x > 0 || self.cursor_position.y > 0{
                    self.move_cursor(Key::Left);
                    self.document.delete(&self.cursor_position);
                }
            }
            Key::Up 
            | Key::Down 
            | Key::Left 
            | Key::Right 
            | Key::PageDown
            | Key::PageUp 
            | Key::Home
            | Key::End => {
                self.move_cursor(key);
            }
            _ => ()
        }
        self.scroll();
        if self.quit_times < QUIT_TIMES {   //reset quit times
            self.quit_times = QUIT_TIMES;
            self.status_msg = StatusMsg::from(String::new());
        }
        Ok(())
    }

    pub fn move_cursor(&mut self, key: Key) {
        let Position { mut x, mut y } = self.cursor_position;
        let document_len = self.document.len();
        let terminal_height = self.terminal.size().height;
        match key {
            Key::Up => {
                y = y.saturating_sub(1);
            }
            Key::Down => {
                y = if y < document_len {
                    y.saturating_add(1)
                }else {
                    y
                }
            }
            Key::Left => {
                x = if x > 0 {
                    x.saturating_sub(1)
                }else if y > 0 {    //change (x, y) to the upper row
                    y -= 1;
                    // self.document.len_of_row(y).saturating_sub(1)
                    self.document.len_of_row(y)
                }else {
                    x
                }
                
            }
            Key::Right => {
                x = if x < self.document.len_of_row(y) {
                    x.saturating_add(1)
                }else if y < document_len.saturating_sub(1) {  //change (x, y) to the next row
                    y += 1;
                    0
                }else {
                    x
                }
                // x = x.saturating_add(1);
            }
            Key::PageUp => {
                y = y.saturating_sub(terminal_height);
            }
            Key::PageDown => {
                y = cmp::min(y.saturating_add(terminal_height), document_len);
            }
            Key::Home => {
                x = 0;
            }
            Key::End => {
                x = self.document.len_of_row(y);
            }
            _ => ()
        }
        //reset the value of x according to the new line
        // x = cmp::min(x, self.document.len_of_row(y).saturating_sub(1));  //enable it if in view mode
        x = cmp::min(x, self.document.len_of_row(y));
        //avoid showing last line of document + 1, that is, a empty line
        // y = cmp::min(y, document_len.saturating_sub(1));

        self.cursor_position = Position{x, y};
    }

    fn scroll(&mut self) {
        let Position { x, y } = self.cursor_position;
        let height_max = self.terminal.size().height.saturating_sub(1);
        let width_max = self.terminal.size().width.saturating_sub(1);

        let mut offset = &mut self.offset_position;
        if y < offset.y {
            offset.y = y;
        }else if y > offset.y.saturating_add(height_max) {
            offset.y = y.saturating_sub(height_max);
        }

        if x < offset.x {
            offset.x = x;
        }else if x > offset.x.saturating_add(width_max) {
            offset.x = x.saturating_sub(width_max);
        }
    }

    /*
     * draw the screen according to the document
     */
    pub fn draw_rows(&mut self) {
        Terminal::cursor_position(&Position::default());

        let height = self.terminal.size().height;
        for terminal_row in 0..height {
            Terminal::clear_current_line();
            if let Some(row) = self.document.row(self.offset_position.y.saturating_add(terminal_row)) {
                self.draw_row(row);
            }else {
                Terminal::fg_color(SYMBOL_FG_COLOR);
                println!("{}\r", self.empty_symbol);
                Terminal::reset_fg_color();
            }
        }
    }

    fn draw_row(&self, row: &Row) {
        let end = self.terminal.size().width;
        let start = self.offset_position.x;
        let end = start + end;
        let row = row.render(start, end);
        println!("{}\r", row);
    }

    pub fn draw_welcome_msg(&mut self) {
        todo!()
    }

    pub fn draw_status_bar(&self) {
        Terminal::clear_current_line();
        let width = self.terminal.size().width;
        let modfied_indicator = if self.document.is_dirty() {
            "(modified)"
        }else {
            ""
        };
        let mut file_name = "[No Name]".to_string();
        if let Some(name) = &self.document.file_name {
            file_name = name.clone();
            file_name.truncate(20);
        }

        let mut status = format!(
            "{} - {} lines{}", 
            file_name, 
            self.document.len(),
            modfied_indicator,
        );

        let line_indicator = format!(
            "{}/{}",
            self.cursor_position.y.saturating_add(1),
            self.document.len(),
        );

        let len = status.len() + line_indicator.len();

        if width > len {
            status.push_str(&" ".repeat(width - len));
        }

        status = format!("{}{}", status, line_indicator);

        status.truncate(width); //abandon the rest bigger than the width of terminal

        Terminal::bg_color(STATUS_BG_COLOR);
        Terminal::fg_color(STATUS_FG_COLOR);

        println!("{}\r", status);
        
        Terminal::reset_bg_color();
        Terminal::reset_fg_color();
    }

    pub fn draw_msg_bar(&self) {
        Terminal::clear_current_line();
        let msg = &self.status_msg;
        if Instant::now() - msg.time < Duration::new(5, 0) {
            let mut text = msg.text.clone();
            text.truncate(self.terminal.size().width);
            print!("{}", text);
        }
    }

    /**
     * msg prompt to user, then get msg from msg bar.
     */
    pub fn prompt(&mut self, prompt: &str) -> Result<Option<String>, io::Error> {
        let mut result = String::new();

        loop {
            self.status_msg = StatusMsg::from(format!("{}{}", prompt, result));
            self.refresh_screen()?;
            match Terminal::read_key()? {
                Key::Char('\n') => {
                    break;
                }
                Key::Backspace => {
                    result.pop();
                }
                Key::Esc => {
                    result.clear();
                    break;
                }
                Key::Char(c) => {
                    if !c.is_control() {
                        result.push(c);
                    }
                }
                _ => ()
            }
        }
        self.status_msg = StatusMsg::from(String::new());
        if result.is_empty() {
            return Ok(None);
        }
        Ok(Some(result))
    }

    pub fn save(&mut self) {
        if self.document.file_name.is_none() {
            let new_name = self.prompt("Save as: ").unwrap_or(None);
            if new_name.is_none() {
                self.status_msg = StatusMsg::from("Save aborted.".to_string());
                return;
            }
            self.document.file_name = new_name;
        }

        if self.document.save().is_ok() {
            self.status_msg = StatusMsg::from("Save successfully.".to_string());
        }else {
            self.status_msg = StatusMsg::from("Error occured!".to_string());
        }
    }


}

impl StatusMsg {
    fn from(msg: String) -> Self{
        Self {
            text: msg,
            time: Instant::now(),
        }
    }
}

/**
 * It will just panic!
 */
fn die(err: io::Error) -> !{
    Terminal::clear_screen();
    panic!("{}", err);
}